package idp.honeytracks;

import weka.classifiers.CostMatrix;
import weka.classifiers.Evaluation;
import weka.classifiers.meta.CostSensitiveClassifier;
import weka.classifiers.meta.FilteredClassifier;
import weka.classifiers.trees.ADTree;
import weka.core.Instances;
import weka.core.Range;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import weka.filters.Filter;
import weka.filters.unsupervised.attribute.Add;
import weka.filters.unsupervised.attribute.MakeIndicator;
import weka.filters.unsupervised.attribute.NominalToBinary;
import weka.filters.unsupervised.attribute.Discretize;
import weka.filters.unsupervised.attribute.NumericToNominal;
import weka.filters.unsupervised.attribute.Remove;
import weka.filters.unsupervised.instance.RemoveWithValues;

public class WekaFunctions {

	private Instances finalDataFirstClass;
	private Instances finalDataThirdClass;
	private Instances finalDataSecondClass;
	private Instances finalDataFourthClass;
	private Instances finalDataFifthClass;
	private Instances test;
	private Instances testFirstClass;
	private Instances testSecondClass;
	private Instances testThirdClass;
	private Instances testFourthClass;
	private Instances testFifthClass;
	
	private Instances testForZeros;
	
	private Instances[] intervalClasses;
	private Instances[] testClasses;
	
	private List<String> intervals;
	
	private double[] bounds;
	private double[] averages;
	private ArrayList<String> zeroInstances;
	
	public void findZeros(int pos_of_class, boolean needsNewClassifier) throws Exception{
		Instances trainForZeros = preprocessForZeros(12, needsNewClassifier);
		//trainForZeros.setClassIndex(pos_of_class-1);
		zeroInstances = new ArrayList<String>();
		 
		FilteredClassifier classifier =  configureZeroClassifier(trainForZeros, needsNewClassifier,pos_of_class);

		 
		for (int i=0;i<testForZeros.numInstances();i++){
			String key = testForZeros.instance(i).stringValue(0);
			classifier.classifyInstance(testForZeros.instance(i));
		
			if (testForZeros.instance(i).classValue() ==1 ){				
				zeroInstances.add(key);
			}
			
		}

	}
	
	public void setInstances(int pos_of_class, boolean needsNewClassifier) throws Exception {
		intervals = new ArrayList<String>();
		bounds = new double[4];
		averages = new double[5];
		
		String posClassStr = Integer.toString(pos_of_class);
		InputStream input = this.getClass().getResourceAsStream("test.arff");
		BufferedReader reader = new BufferedReader(new InputStreamReader(input,
				"UTF-8"));

		test = new Instances(reader);

		Instances initialData= null; 
		Instances data = null;
		if (needsNewClassifier){
			input = this.getClass().getResourceAsStream("data.arff");
			reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
			data = new Instances(reader);
			initialData = new Instances(data); 
		}
			
		reader.close();
		
		RemoveWithValues rmWithVal = new RemoveWithValues();
		rmWithVal.setAttributeIndex(posClassStr);
		rmWithVal.setSplitPoint(0.01);
		if (needsNewClassifier){
			rmWithVal.setInputFormat(data);
			data = Filter.useFilter(data, rmWithVal);
		}		
		
		
		if (test.numAttributes() == pos_of_class){
			Add addFilter  = new Add();
			addFilter.setAttributeIndex("last");
			addFilter.setAttributeName("unnamed");
			if (needsNewClassifier){
				addFilter.setInputFormat(data);
				data = Filter.useFilter(data, addFilter);
			}else{
				addFilter.setInputFormat(test);
			}
			test = Filter.useFilter(test, addFilter);
		}

		Discretize disctrFilter = new Discretize();
		disctrFilter.setBins(5);
		disctrFilter.setAttributeIndices(posClassStr);
		disctrFilter.setUseEqualFrequency(true);
		
		
			if (needsNewClassifier){
				disctrFilter.setInputFormat(data);
				data = Filter.useFilter(data, disctrFilter);
			}else{
				disctrFilter.setInputFormat(test);
			}
		
		test = Filter.useFilter(test, disctrFilter);
		
		
		NominalToBinary nomToBinFilter = new NominalToBinary();

		nomToBinFilter.setAttributeIndices(posClassStr);
		if (needsNewClassifier){
			nomToBinFilter.setInputFormat(data);
			data = Filter.useFilter(data, nomToBinFilter);
		}else{
			nomToBinFilter.setInputFormat(test);
		}
		test = Filter.useFilter(test, nomToBinFilter);
		

		NumericToNominal numToNomFilter2 = new NumericToNominal();		
		numToNomFilter2.setAttributeIndices(posClassStr +"-"+ Integer.toString(pos_of_class + 4));
		if (needsNewClassifier){
			numToNomFilter2.setInputFormat(data);
			data = Filter.useFilter(data, numToNomFilter2);
		}else{
			numToNomFilter2.setInputFormat(test);
		}
		test = Filter.useFilter(test, numToNomFilter2);
		
		Remove removeFilterDummy = new Remove();
		removeFilterDummy.setAttributeIndices(Integer.toString(pos_of_class+5));
		if (needsNewClassifier){
			removeFilterDummy.setInputFormat(data);
			data = Filter.useFilter(data, removeFilterDummy);
		}else{
			removeFilterDummy.setInputFormat(test);
		}
		test = Filter.useFilter(test, removeFilterDummy);

		Remove removeFilter = new Remove();
		removeFilter.setAttributeIndices(Integer.toString(pos_of_class+1)+"-"+ 
						Integer.toString(pos_of_class + 4));
		if (needsNewClassifier){
			removeFilter.setInputFormat(data);		
			finalDataFirstClass = Filter.useFilter(data, removeFilter);
			intervals.add(finalDataFirstClass.attribute(pos_of_class-1).name());
		}else{
			removeFilter.setInputFormat(test);
		}
		testFirstClass = Filter.useFilter(test, removeFilter);

		
		Remove removeFilter2 = new Remove();
		removeFilter2.setAttributeIndices(posClassStr +","+ 
						Integer.toString(pos_of_class + 2)+"-"+Integer.toString(pos_of_class + 4));
		if (needsNewClassifier){	
			removeFilter2.setInputFormat(data);
			finalDataSecondClass = Filter.useFilter(data, removeFilter2);
			intervals.add(finalDataSecondClass.attribute(pos_of_class-1).name());
		}else{
			removeFilter2.setInputFormat(test);
		}
		testSecondClass = Filter.useFilter(test, removeFilter2);

		
		Remove removeFilter3 = new Remove();
		removeFilter3.setAttributeIndices(posClassStr+"-"+Integer.toString(pos_of_class + 1)+","+
									Integer.toString(pos_of_class+3)+"-"+Integer.toString(pos_of_class+4));
		if (needsNewClassifier){
			removeFilter3.setInputFormat(data);
			finalDataThirdClass = Filter.useFilter(data, removeFilter3);
			intervals.add(finalDataThirdClass.attribute(pos_of_class-1).name());
		}else{
			removeFilter3.setInputFormat(test);
		}
		testThirdClass = Filter.useFilter(test, removeFilter3);
		

		Remove removeFilter4 = new Remove();
		removeFilter4.setAttributeIndices(posClassStr+"-"+Integer.toString(pos_of_class + 2)+","+ 
									Integer.toString(pos_of_class + 4));
		if (needsNewClassifier){
			removeFilter4.setInputFormat(data);
			finalDataFourthClass = Filter.useFilter(data, removeFilter4);
			intervals.add(finalDataFourthClass.attribute(pos_of_class-1).name());
		}else{
			removeFilter4.setInputFormat(test);
		}
		testFourthClass = Filter.useFilter(test, removeFilter4);
		

		Remove removeFilter5 = new Remove();
		removeFilter5.setAttributeIndices(posClassStr+"-"+Integer.toString(pos_of_class+3));
		if (needsNewClassifier){
			removeFilter5.setInputFormat(data);
			finalDataFifthClass = Filter.useFilter(data, removeFilter5);
			intervals.add(finalDataFifthClass.attribute(pos_of_class-1).name());
		}else{
			removeFilter5.setInputFormat(test);
		}
		testFifthClass = Filter.useFilter(test, removeFilter5);

		if (needsNewClassifier){
			
			for (int l=1;l<intervals.size();l++){
				String name = intervals.get(l);						
				name = name.substring(name.indexOf("(")+1, name.indexOf("-"));				
				bounds[l-1] = Double.parseDouble(name);			
			}
			
			double sumFirst=0, sumSecond=0, sumThird=0, sumFourth=0, sumFifth = 0;
			int firsts =0, seconds =0, thirds =0, fourths = 0, fifths = 0;
				for (int i=0;i<initialData.numInstances();i++){
						double revenue = initialData.instance(i).value(pos_of_class-1);
						if (revenue>bounds[3]){
							sumFifth +=revenue;
							fifths ++;
						}else if (revenue >bounds[2]){
							sumFourth += revenue;
							fourths++;
						}else if (revenue >bounds[1]){
							sumThird +=revenue;
							thirds++;
						}else if (revenue >bounds[0]){
							sumSecond +=revenue;
							seconds++;
						}else{
							sumFirst +=revenue;
							firsts++;
						}
				}
							
				averages[0] = sumFirst / firsts;
				averages[1] = sumSecond/seconds;
				averages[2] = sumThird/thirds;
				averages[3] = sumFourth/fourths;
				averages[4] = sumFifth/fifths;
		
				FileWriter fstream = new FileWriter("trainInfo.txt");
				BufferedWriter out = new BufferedWriter(fstream);
				
				
				out.write(Double.toString(averages[0])+"\n");
				out.write(Double.toString(averages[1])+"\n");
				out.write(Double.toString(averages[2])+"\n");
				out.write(Double.toString(averages[3])+"\n");
				out.write(Double.toString(averages[4]));
				
			out.close();
		
			intervalClasses = new Instances[5];
			intervalClasses[0]=  finalDataFirstClass;
			intervalClasses[1]=  finalDataSecondClass;
			intervalClasses[2]=  finalDataThirdClass;
			intervalClasses[3]=  finalDataFourthClass;
			intervalClasses[4]=  finalDataFifthClass;
		
		}
		
		testClasses = new Instances[5];
		testClasses[0]=  testFirstClass;
		testClasses[1]=  testSecondClass;
		testClasses[2]=  testThirdClass;
		testClasses[3]=  testFourthClass;
		testClasses[4]=  testFifthClass;
	}

	

	private Instances preprocessForZeros(int pos_of_class, boolean needsNewClassifier) throws Exception{
		Instances dataForZeros= null;
		if (needsNewClassifier){
		InputStream input = this.getClass().getResourceAsStream("data.arff");
		BufferedReader reader = new BufferedReader(new InputStreamReader(input,
				"UTF-8"));

		dataForZeros = new Instances(reader);

		dataForZeros.setClassIndex(pos_of_class-1);
		}
		InputStream input = this.getClass().getResourceAsStream("test.arff");
		BufferedReader reader = new BufferedReader(new InputStreamReader(input,
				"UTF-8"));

		 testForZeros = new Instances(reader);
		 testForZeros.setClassIndex(pos_of_class-1);
		
		reader.close();

		
		if (testForZeros.numAttributes() == pos_of_class){
			Add addFilter  = new Add();
			addFilter.setAttributeIndex("last");
			addFilter.setAttributeName("unnamed");
			if (needsNewClassifier){
				addFilter.setInputFormat(dataForZeros);
				dataForZeros = Filter.useFilter(dataForZeros, addFilter);
			}else{
				addFilter.setInputFormat(testForZeros);
			}
			testForZeros = Filter.useFilter(testForZeros, addFilter);
		}
		
		NumericToNominal numToNominal = new NumericToNominal();
		numToNominal.setAttributeIndices(Integer.toString(pos_of_class));
		if (needsNewClassifier){
		numToNominal.setInputFormat(dataForZeros);
		dataForZeros = Filter.useFilter(dataForZeros, numToNominal);
		}
		numToNominal.setInputFormat(testForZeros);
		testForZeros = Filter.useFilter(testForZeros, numToNominal);
		
		
		MakeIndicator makeIndicatorFilter = new MakeIndicator();
		makeIndicatorFilter.setAttributeIndex(Integer.toString(pos_of_class));
		makeIndicatorFilter.setValueIndices("1");
		if (needsNewClassifier){
		makeIndicatorFilter.setInputFormat(dataForZeros);
		dataForZeros = Filter.useFilter(dataForZeros, makeIndicatorFilter);
		}
		makeIndicatorFilter.setInputFormat(testForZeros);
		testForZeros = Filter.useFilter(testForZeros, makeIndicatorFilter);
		
		NumericToNominal numToNominalFilter = new NumericToNominal();
		numToNominalFilter.setAttributeIndices(Integer.toString(pos_of_class));
		if (needsNewClassifier){
		numToNominalFilter.setInputFormat(dataForZeros);
		dataForZeros = Filter.useFilter(dataForZeros, numToNominalFilter);
		}
		numToNominalFilter.setInputFormat(testForZeros);
		testForZeros = Filter.useFilter(testForZeros, numToNominalFilter);	
			
		
		return dataForZeros;
	}
	
	
	public void testGenerate(int posClassAttrib, boolean needsNewClassifier) throws Exception {
		int j = 0;
		Map<String, ArrayList<Double>> probMap = new HashMap<String, ArrayList<Double>>();
		
	//	for (Instances instancesClass : intervalClasses) {
		for (int k=0;k<5;k++){
			FilteredClassifier classifier ;
			if (needsNewClassifier){
				intervalClasses[k].setClassIndex(posClassAttrib);
				classifier =  configureClassifier(intervalClasses[k], needsNewClassifier, j);
			}else{
				classifier = configureClassifier(null, needsNewClassifier, j);
			}
			testClasses[j].setClassIndex(posClassAttrib);
			for (int i = 0; i < testClasses[j].numInstances(); i++) {		
					
				String key = testClasses[j].instance(i).stringValue(0);
				classifier.classifyInstance(testClasses[j].instance(i));
	
				double[] distributions= classifier.distributionForInstance(testClasses[j].instance(i));
				ArrayList<Double> arr = probMap.get(key);
				if (arr == null){
					arr = new ArrayList<Double>();
				}
				arr.add(distributions[1]);
				probMap.put(key, arr);
				
			}
			j++;
		}
		
		Map<String, Double> resultsMap = new HashMap<String, Double>();
		if (!needsNewClassifier){
			FileInputStream fstream = new FileInputStream("trainInfo.txt");
			  DataInputStream in = new DataInputStream(fstream);
			  BufferedReader br = new BufferedReader(new InputStreamReader(in));
			  String strLine;
			  int i =0;
			  while ((strLine = br.readLine()) != null)   {				  
				  averages[i] = Double.parseDouble(strLine);
					i++;
			  }
			  in.close();
		}

		InputStream input = this.getClass().getResourceAsStream("urls.txt");
		BufferedReader reader = new BufferedReader(new InputStreamReader(input,
				"UTF-8"));
		String line, store_url="", username="", password="",domain="";
		int i=0;
		while ((line = reader.readLine()) != null) {
			if (i==1){
				store_url=line.substring(line.indexOf("=")+1);
			}else if (i==2){
				username=line.substring(line.indexOf("=")+1);
			}else if (i==3){
				password=line.substring(line.indexOf("=")+1);
			}else if (i==4){
				domain=line.substring(line.indexOf("=")+1);
			}
			i++;
		}
		
		for (int k =0; k<probMap.keySet().toArray().length;k++){
			double formulaSum =0; 
			double formulaSumWithoutAvg =0;

			double finalRevenue=0;
			if (!zeroInstances.contains(probMap.keySet().toArray()[k].toString())){
			ArrayList<Double> prob = probMap.get(probMap.keySet().toArray()[k]);
			
			for (int l =0; l<prob.size();l++){
				
				double p = ((prob.get(l) < 0.5) ? 0 : prob.get(l) -0.7);
				if (p!=0){
					formulaSumWithoutAvg += Math.exp(p);
					formulaSum += (Math.exp(p)*averages[l]);
				}
			}
			if (formulaSumWithoutAvg == 0){
				double prob0 = Math.exp(prob.get(0));
				double prob1 = Math.exp(prob.get(1));
				double prob2 = Math.exp(prob.get(2));
				double prob3 = Math.exp(prob.get(3));
				double prob4 = Math.exp(prob.get(4));
				finalRevenue = (prob0*averages[0] + prob1*averages[1]+prob2*averages[2]+prob3*averages[3]+prob4*averages[4])/(prob0+prob1+prob2+prob3+prob4);
			}else{
				finalRevenue = formulaSum / formulaSumWithoutAvg;
			}
			}
			resultsMap.put(probMap.keySet().toArray()[k].toString(), finalRevenue);
			//System.out.println("User with id = " +probMap.keySet().toArray()[k].toString()+" pays "+finalRevenue);
			
			
			RestService restService = new RestService(username,password, "HoneyTracks Internal API Access",domain, 443);
//			String data = restService.fetchData("https://api.honeytracks.com/Api/REST/Data/CustomQueries/SQL1/1047/?format=json");
			restService.submitResult(store_url, probMap.keySet().toArray()[k].toString(), finalRevenue);

		}
		

	}
	
	private FilteredClassifier configureClassifier(Instances instances, boolean needsNewClassifier, int index)
			throws Exception {
		FilteredClassifier filteredClassifier= new FilteredClassifier();
		if (needsNewClassifier){
		InputStream inputMatrix = this.getClass().getResourceAsStream(
				"big_dump.cost");
		BufferedReader readerMatrix = new BufferedReader(new InputStreamReader(
				inputMatrix, "UTF-8"));

		CostMatrix matrix = new CostMatrix(readerMatrix);
		
		ADTree tree = new ADTree();
		tree.setNumOfBoostingIterations(10);
		tree.setRandomSeed(0);
		tree.setSaveInstanceData(false);
		

		CostSensitiveClassifier classifier = new CostSensitiveClassifier();
		classifier.setClassifier(tree);
		classifier.setCostMatrix(matrix);
		classifier.setSeed(1);

		
		Remove ignoreId = new Remove();
		ignoreId.setAttributeIndices("1");
		filteredClassifier.setFilter(ignoreId);
		filteredClassifier.setClassifier(classifier);
		filteredClassifier.buildClassifier(instances);
		ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("classifier"+index+".model"));
		oos.writeObject(filteredClassifier);
		oos.flush();
		oos.close();
		}
		else{
			 ObjectInputStream ois = new ObjectInputStream(
                     new FileInputStream("classifier"+index+".model"));
			 filteredClassifier =  (FilteredClassifier) ois.readObject();
			 ois.close();
		}
		return filteredClassifier;
	}
	
	private FilteredClassifier configureZeroClassifier(Instances train, boolean needsNewClassifier, int index) throws Exception{
		FilteredClassifier filteredClassifier= new FilteredClassifier();
		if (needsNewClassifier){
		InputStream inputMatrix = this.getClass().getResourceAsStream(
				"big_dumpForZero.cost");
		BufferedReader readerMatrix = new BufferedReader(new InputStreamReader(
				inputMatrix, "UTF-8"));

		CostMatrix matrix = new CostMatrix(readerMatrix);
		
		ADTree tree = new ADTree();
		tree.setNumOfBoostingIterations(10);
		tree.setRandomSeed(0);
		tree.setSaveInstanceData(false);
		

		CostSensitiveClassifier classifier = new CostSensitiveClassifier();
		classifier.setClassifier(tree);
		classifier.setCostMatrix(matrix);
		classifier.setSeed(1);

		
		Remove ignoreId = new Remove();
		ignoreId.setAttributeIndices("1");
		filteredClassifier.setFilter(ignoreId);
		filteredClassifier.setClassifier(classifier);
		filteredClassifier.buildClassifier(train);
		ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("zeroclassifier"+index+".model"));
		oos.writeObject(filteredClassifier);
		oos.flush();
		oos.close();
		}
		else{
			 ObjectInputStream ois = new ObjectInputStream(
                     new FileInputStream("zeroclassifier"+index+".model"));
			 filteredClassifier =  (FilteredClassifier) ois.readObject();
			 ois.close();
			 
		}
		return filteredClassifier;
	}
	
}



















